//BeanCipher - Bean Cipher Security Layer
//Copyright (C) 2005 Raul Alfredo Bajales
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//Lesser General Public License for more details.
//
//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package ar.com.rab.beancipher.impl;

import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;

import ar.com.rab.beancipher.impl.configuration.Configuration;
import ar.com.rab.beancipher.spi.IBeanDescriptor;
import ar.com.rab.beancipher.spi.IConstants;
import ar.com.rab.beancipher.spi.IPropertyCipher;
import ar.com.rab.beancipher.spi.IPropertyDescriptor;
import ar.com.rab.beancipher.spi.exceptions.BeanCipherException;
import ar.com.rab.beancipher.spi.exceptions.PropertyCipherException;

/**
 * Default implementation (extending AbstractBeanCipher).
 * It receives the configuration in an argument for the constructor 
 * and delegates the operations (cihper / decipher) on the corresponding
 * property ciphers.
 * 
 * @see ar.com.rab.beancipher.impl.configuration.Configuration
 * @author Bajales Raul
 */
public class DefaultBeanCipher extends AbstractBeanCipher {

    protected Configuration configuration;
    
    /**
     * Constructor
     * 
     * Calls the AbstractBeanCipher constructor passing the loaded configuration
     * 
     * @param configuration
     */
    public DefaultBeanCipher(Configuration configuration) {
        super();
        this.configuration = configuration;
    }

    /* (non-Javadoc)
     * @see ar.com.rab.beancipher.spi.impl.AbstractBeanCipher#doCipherBean(java.lang.Object)
     */
    protected void doCipherBean(Object aBean) throws BeanCipherException {
        try {
            cipherOrDecipherBean(aBean, IConstants.DO_CIPHER);
        } catch (Exception e) {
            throw new BeanCipherException(e);
        }
    }

    /* (non-Javadoc)
     * @see ar.com.rab.beancipher.spi.impl.AbstractBeanCipher#doDeCipherBean(java.lang.Object)
     */
    protected void doDeCipherBean(Object aBean) throws BeanCipherException {
        try {
            cipherOrDecipherBean(aBean, IConstants.DO_DECIPHER);
        } catch (Exception e) {
            throw new BeanCipherException(e);
        }
    }

    /* (non-Javadoc)
     * @see ar.com.rab.beancipher.impl.AbstractBeanCipher#reloadConfiguration()
     */
    public void reloadConfiguration() throws BeanCipherException {
        this.configuration.reload();
    }

    /**
     * Obtains a bean descriptor for the bean, iterates all property descriptors,
     * for each one, search a bean descriptor for the class name of the property,
     * if found, recurse the operation, if not applies a property cipher for each 
     * property to be ciphered / deciphered depending on the value of 
     * the argument <code>action</code>
     * 
     * @param aBean
     * @param action
     * @throws PropertyCipherException
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws BeanCipherException
     */
    private void cipherOrDecipherBean(Object aBean, int action) throws PropertyCipherException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, BeanCipherException {
        IBeanDescriptor descriptor = this.configuration.getDescriptorFor(aBean.getClass().getName());
        if (descriptor == null) 
            throw new BeanCipherException("Can't cipher or decipher instance of '" + aBean.getClass().getName() + "': descriptor not found.");
        IPropertyCipher propertyCipher = descriptor.getPropertyCipher();
        List properties = descriptor.getPropertyDescriptors();
        for (Iterator iter = properties.iterator(); iter.hasNext();) {
            IPropertyDescriptor propertyDescriptor = (IPropertyDescriptor) iter.next();
            IBeanDescriptor propertyClassDescriptor = this.configuration.getDescriptorFor(propertyDescriptor.getClassName());
            if (propertyClassDescriptor == null) 
                workOnProperty(aBean, action, propertyCipher, propertyDescriptor);
            else 
                workOnBean(aBean, action, propertyDescriptor);
        }
    }

    /**
     * Modify the property of the bean applying BeanCipher to the property value.
     * 
     * @param aBean
     * @param action
     * @param propertyDescriptor
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws BeanCipherException
     */
    private void workOnBean(Object aBean, int action, IPropertyDescriptor propertyDescriptor) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, BeanCipherException {
        Object object = propertyDescriptor.getGetter().invoke(aBean, new Object[] {});
        switch (action) {
            case IConstants.DO_CIPHER:
                super.cipherBean(object);
                break;
            case IConstants.DO_DECIPHER:
                super.deCipherBean(object);
                break;
        }
        propertyDescriptor.getSetter().invoke(aBean, new Object[] {object});
    }

    /**
     * Modify the property of the bean applying PropertyCipher to the property value.
     * 
     * @param aBean
     * @param action
     * @param propertyCipher
     * @param propertyDescriptor
     * @throws PropertyCipherException
     */
    private void workOnProperty(Object aBean, int action, IPropertyCipher propertyCipher, IPropertyDescriptor propertyDescriptor) throws PropertyCipherException {
        switch (action) {
            case IConstants.DO_CIPHER:
                propertyCipher.cipherProperty(aBean, propertyDescriptor);
                break;
            case IConstants.DO_DECIPHER:
                propertyCipher.deCipherProperty(aBean, propertyDescriptor);
                break;
        }
    }
}
